package com.wanna.common.net;

import com.alibaba.fastjson.JSON;
import com.wanna.common.io.FileUtil;
import com.wanna.common.lang.StringUtil;
import com.wanna.common.util.Constant;
import com.wanna.common.util.Encrypt;
import com.wanna.common.util.QuietlyUtil;
import org.apache.commons.io.FileUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;

/**
 * @author wanna
 * @since 2018-5-25
 */
public class Download {

    private static final String DOWNLOADS = "Downloads";

    /**
     * 存放路径
     */
    private String savePath;

    /**
     * 下载链接
     */
    private String downloadURL;

    /**
     * 线程数
     */
    private int threadCount;

    /**
     * 是否暂停
     */
    private AtomicBoolean pause = new AtomicBoolean(false);

    /**
     * 总大小
     */
    private final LongAdder totalSize = new LongAdder();

    /**
     * 获取任务结果
     */
    private List<Future<Boolean>> tasks = new ArrayList<>();

    /**
     * 用来计算当前已完成多少
     */
    private volatile List<DownloadTask> downloadTasks = Collections.synchronizedList(new ArrayList<>());

    /**
     * 计数器
     */
    private CountDownLatch count;

    /**
     * 下载的文件
     */
    private File downloadFile;

    /**
     * 用来存储下载目标文件大小(用来计算当前下载完成百分比)
     */
    private long fileSize = 0L;

    public Download(String downloadURL) {
        this(downloadURL, Runtime.getRuntime().availableProcessors());
    }

    public Download(String downloadURL, int threadCount) {
        this(downloadURL, getDefaultSavePath(), threadCount);
    }

    public Download(String downloadURL, String savePath, int threadCount) {
        this.downloadURL = downloadURL;
        this.savePath = savePath;
        this.threadCount = threadCount;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 下载
     *
     * @throws IOException IOException
     */
    public void download() throws IOException {
        pause.set(false);
        // 先获取目标文件大小
        getDownloadFileSize();

        // 初始化待下载的文件
        initDownloadFile();

        // 开始下载
        startDownload();
    }

    /**
     * 暂停
     */
    public void pause() throws Exception {
        pause.set(true);
        String filePath = getRecordFilePath();
        StringBuilder builder = new StringBuilder();

        count.await();

        for (DownloadTask task : downloadTasks) {
            builder.append(JSON.toJSON(task.getPosition())).append(Constant.NEW_LINE);
        }
        FileUtils.write(new File(filePath), builder.toString(), Constant.UTF8);
    }

    /**
     * requestGet current percentage
     *
     * @return 0.15 % 53.69 %
     */
    public synchronized String getCurrentPercentage() {
        long currentSize = totalSize.sum();
        currentSize = currentSize > fileSize ? fileSize : currentSize;
        return String.format("%.2f", (currentSize * 100.0 / fileSize)) + " %";
    }

    /**
     * is done
     *
     * @return true or false
     */
    public boolean isDone() {
        for (Future<Boolean> task : tasks) {
            if (!task.isDone()) {
                return false;
            }
        }

        // 完成之后删除临时文件
        String path = getRecordFilePath();
        File file = new File(path);
        FileUtils.deleteQuietly(file);
        return true;
    }

    /**
     * 获取默认的下载目录
     *
     * @return 存放的路径
     */
    private static String getDefaultSavePath() {
        return System.getProperty("user.home") + File.separator + DOWNLOADS;
    }

    /**
     * 初始化下载的文件
     *
     * @throws IOException IOException
     */
    private void initDownloadFile() throws IOException {
        File file = new File(savePath);
        // 创建文件夹
        FileUtil.mkdirs(file);

        // 创建文件
        String fileName = fixDownloadFilePath();
        downloadFile = new File(fileName);
        FileUtil.createNewFile(downloadFile);
    }

    /**
     * 修复下载文件的完整路径
     *
     * @return
     */
    private String fixDownloadFilePath() {
        return savePath + fixFileName();
    }

    /**
     * 文件名
     *
     * @return
     */
    private String fixFileName() {
        int indexOf = StringUtil.remove(downloadURL, "/").lastIndexOf("/");
        indexOf = indexOf == -1 ? downloadURL.lastIndexOf("/") : indexOf;
        return downloadURL.substring(indexOf, downloadURL.length());
    }

    private List<Position> getLastPositions(File file) throws IOException {

        List<Position> positions = new ArrayList<>();
        List<String> list = FileUtils.readLines(file, Constant.UTF8);

        for (String str : list) {
            Position position = JSON.parseObject(str, Position.class);
            long startIndex = position.getStartIndex();
            long pauseIndex = position.getPauseIndex();
            long start = pauseIndex > startIndex ? pauseIndex : startIndex;
            position.setStartIndex(start);
            positions.add(position);
        }

        return positions;
    }

    private String getRecordFilePath() {
        // 这样每次保存路径不一样的时候,MD5值也会不一样
        String fileName = fixDownloadFilePath();

        String tmpPath = System.getProperty("java.io.tmpdir");
        return tmpPath + Encrypt.md5(fileName).substring(0, 10);
    }

    private void startDownload() throws IOException {
        String path = getRecordFilePath();
        List<Position> positions = initPositions(path);
        // 直接设置上次下载的值
        setLastDownloadSize(positions);

        ExecutorService pool = Executors.newFixedThreadPool(positions.size());
        count = new CountDownLatch(positions.size());

        for (Position position : positions) {
            DownloadTask task = new DownloadTask(position, count);
            downloadTasks.add(task);
            tasks.add(pool.submit(task));
        }

        pool.shutdown();
    }

    private void setLastDownloadSize(List<Position> positions) {
        long sum = positions.stream().mapToLong(Position::getDownloadSize).sum();
        totalSize.add(sum);
    }

    private List<Position> initPositions(String path) throws IOException {
        File file = new File(path);

        if (file.exists()) {
            return getLastPositions(file);
        } else {
            return getFirstPositions();
        }
    }

    private List<Position> getFirstPositions() {
        List<Position> positions = new ArrayList<>();
        long taskSize = fileSize / threadCount;
        long modSize = fileSize % threadCount;

        for (int i = 0; i < threadCount; i++) {
            long startPosition = taskSize * i;
            // 不然可能造成文件损坏
            long endPosition = taskSize * (i + 1) + modSize;
            positions.add(new Position(startPosition, endPosition));
        }
        return positions;
    }

    /**
     * 获取服务器文件大小
     *
     * @throws IOException IOException
     */
    private void getDownloadFileSize() throws IOException {
        HttpClient httpClient = new HttpClient(downloadURL);
        httpClient.openConnection();
        fileSize = httpClient.getConnection().getContentLengthLong();
        httpClient.disconnect();
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * DownloadTask 下载的任务线程
     */
    private class DownloadTask implements Callable<Boolean> {

        private final static int BUFFER = 4096;

        private Position position;

        private CountDownLatch count;

        private long writeCount;

        public DownloadTask(Position position, CountDownLatch count) {
            this.position = position;
            this.count = count;
        }

        @Override
        public Boolean call() {
            HttpClient client = new HttpClient(downloadURL);
            InputStream inputStream = null;
            RandomAccessFile rw = null;

            try {
                connectServer(client);
                inputStream = client.getInputStream();
                rw = new RandomAccessFile(downloadFile, "rw");
                rw.seek(position.getStartIndex());

                readAndWrite(rw, inputStream);
                return true;
            } catch (FileNotFoundException e) {
                return false;
            } catch (IOException e) {
                return false;
            } finally {
                count.countDown();
                client.disconnect();
                QuietlyUtil.close(inputStream, rw);
            }
        }

        private void connectServer(HttpClient client) throws IOException {
            client.openConnection();
            client.addRequestProperty("Range", "bytes=" + position.getStartIndex() + "-" + position.getEndIndex());
            client.connect();
        }

        /**
         * 读写数据
         *
         * @param rw          randomAccessFile
         * @param inputStream inputStream
         * @throws IOException IOException
         */
        private void readAndWrite(RandomAccessFile rw, InputStream inputStream) throws IOException {
            byte[] buffer = new byte[BUFFER];
            int hasRead;
            while ((hasRead = inputStream.read(buffer)) > 0) {
                if (pause.get()) {
                    // 记录暂停的位置
                    position.setPauseIndex(position.getStartIndex() + writeCount);
                    long downloadSize = position.getDownloadSize() + writeCount;
                    position.setDownloadSize(downloadSize);
                    break;
                } else {
                    rw.write(buffer, 0, hasRead);
                    totalSize.add(hasRead);
                    writeCount += hasRead;
                }
            }
        }

        public long getWriteCount() {
            return writeCount;
        }

        public Position getPosition() {
            return position;
        }
    }

}
